#!/usr/bin/env python3

"""Export top representative posts for a specified stance across lifecycle phases.

This utility mirrors the representative-post generation embedded in
``build_metrics.py`` but lets analysts focus on a single stance (e.g. Oppose)
without re-running the full pipeline. The output schema matches
``representative_posts_top10_per_phase.csv`` so downstream tooling can reuse
the artefact interchangeably.
"""

from __future__ import annotations

import argparse
from pathlib import Path
from typing import Iterable

import pandas as pd

from build_metrics import (
    PHASE_ORDER,
    STANCE_MAP,
    build_candidates,
    ensure_column,
    normalize_phase,
    normalize_stance,
    to_numeric,
)

DEFAULT_ENCODING = "gb18030"


def parse_args(argv: Iterable[str] | None = None) -> argparse.Namespace:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument(
        "--input",
        type=Path,
        required=True,
        help="Path to the lifecycle-labelled CSV (must include cycle_phrase).",
    )
    parser.add_argument(
        "--stance",
        required=True,
        help=(
            "Stance to export (accepts English labels like 'Oppose' or Chinese"
            " labels such as '反对')."
        ),
    )
    parser.add_argument(
        "--output",
        type=Path,
        help=(
            "Destination CSV path. Defaults to "
            "'<input_stem>_<stance>_top<limit>_per_phase.csv' next to the input."
        ),
    )
    parser.add_argument(
        "--limit",
        type=int,
        default=10,
        help="Maximum records per phase (default: 10).",
    )
    parser.add_argument(
        "--encoding",
        default=DEFAULT_ENCODING,
        help=f"Encoding for I/O (default: {DEFAULT_ENCODING}).",
    )
    parser.add_argument(
        "--time-column",
        help="Preferred time column (defaults cascade through publish_time/time aliases).",
    )
    parser.add_argument(
        "--time-alias",
        action="append",
        dest="time_aliases",
        help="Additional time column aliases.",
    )
    parser.add_argument(
        "--position-column",
        help="Preferred stance column (defaults cascade through position/立场 aliases).",
    )
    parser.add_argument(
        "--position-alias",
        action="append",
        dest="position_aliases",
        help="Additional stance column aliases.",
    )
    parser.add_argument(
        "--like-column",
        help="Preferred like-count column (defaults cascade through likes_count etc.).",
    )
    parser.add_argument(
        "--comment-column",
        help="Preferred comment-count column.",
    )
    parser.add_argument(
        "--forward-column",
        help="Preferred share/forward-count column.",
    )
    parser.add_argument(
        "--viewpoint-column",
        help="Preferred column for viewpoint/summary text.",
    )
    parser.add_argument(
        "--phase-column",
        help="Column storing lifecycle codes; defaults to 'cycle_phrase'.",
    )
    return parser.parse_args(argv)


def normalize_stance_input(value: str) -> str:
    """Map user-supplied stance labels to the normalized English form."""
    text = value.strip()
    if not text:
        raise ValueError("Stance value cannot be empty.")

    # Exact English match.
    for label in STANCE_MAP.values():
        if text.lower() == label.lower():
            return label

    # Exact Chinese (or other key) match.
    if text in STANCE_MAP:
        return STANCE_MAP[text]

    raise ValueError(
        f"Unrecognised stance '{value}'. Accepted English labels: "
        f"{', '.join(sorted(set(STANCE_MAP.values())))}."
    )


def prepare_dataframe(args: argparse.Namespace) -> pd.DataFrame:
    df_raw = pd.read_csv(args.input, encoding=args.encoding)

    time_candidates = build_candidates(
        args.time_column,
        args.time_aliases or [],
        ["time", "publish_time", "发布时间", "發佈時間", "时 间"],
    )
    position_candidates = build_candidates(
        args.position_column,
        args.position_aliases or [],
        ["position", "立场", "立場", "stance"],
    )
    like_candidates = build_candidates(
        args.like_column,
        [],
        ["Like", "likes_count", "点赞数", "按讚數"],
    )
    comment_candidates = build_candidates(
        args.comment_column,
        [],
        ["Comment", "comments_count", "评论数", "留言數"],
    )
    forward_candidates = build_candidates(
        args.forward_column,
        [],
        ["Forward", "share_count", "reposts_count", "分享数", "轉發數"],
    )
    viewpoint_candidates = build_candidates(
        args.viewpoint_column,
        [],
        ["Viewpoint", "观点", "觀點", "Viewpoint_CN"],
    )
    phase_candidates = build_candidates(
        args.phase_column,
        [],
        ["cycle_phrase", "phase_code"],
    )

    df = ensure_column(df_raw, "time", time_candidates, required=True)
    df = ensure_column(df, "position", position_candidates, required=True)
    df = ensure_column(df, "Like", like_candidates, required=False, fill_value=0)
    df = ensure_column(df, "Comment", comment_candidates, required=False, fill_value=0)
    df = ensure_column(df, "Forward", forward_candidates, required=False, fill_value=0)
    df = ensure_column(df, "Viewpoint", viewpoint_candidates, required=False, fill_value="")
    df = ensure_column(df, "BEND", ["BEND"], required=False, fill_value="")
    df = ensure_column(
        df,
        "BEND_Reasoning",
        ["BEND_Reasoning"],
        required=False,
        fill_value="",
    )
    df = ensure_column(
        df,
        "cycle_phrase",
        phase_candidates,
        required=True,
    )

    df["phase"] = normalize_phase(df["cycle_phrase"])
    df["stance"] = normalize_stance(df["position"])
    df["Like"] = to_numeric(df["Like"]).astype(int)
    df["Comment"] = to_numeric(df["Comment"]).astype(int)
    df["Forward"] = to_numeric(df["Forward"]).astype(int)
    df["interactions"] = df["Like"] + df["Comment"] + df["Forward"]

    timestamps = pd.to_datetime(df["time"], errors="coerce")
    df["time"] = timestamps.dt.strftime("%Y/%m/%d %H:%M").fillna("")

    for column in ["content", "Viewpoint", "BEND", "BEND_Reasoning"]:
        if column in df.columns:
            df[column] = df[column].fillna("")
        else:
            df[column] = ""

    return df


def export_representatives(df: pd.DataFrame, stance_label: str, limit: int) -> pd.DataFrame:
    subset = df[(df["stance"] == stance_label) & df["phase"].notna()].copy()
    if subset.empty:
        return pd.DataFrame(
            columns=[
                "phase",
                "stance",
                "time",
                "interactions",
                "like",
                "comment",
                "forward",
                "content_snippet",
                "viewpoint_snippet",
                "bend",
                "bend_reasoning",
            ]
        )

    records: list[dict[str, object]] = []
    for phase in PHASE_ORDER:
        group = subset[subset["phase"] == phase]
        if group.empty:
            continue
        top = group.sort_values("interactions", ascending=False).head(limit)
        for _, row in top.iterrows():
            records.append(
                {
                    "phase": phase,
                    "stance": row["stance"],
                    "time": row["time"],
                    "interactions": int(row["interactions"]),
                    "like": int(row["Like"]),
                    "comment": int(row["Comment"]),
                    "forward": int(row["Forward"]),
                    "content_snippet": str(row.get("content", "")).replace("\n", " ").strip(),
                    "viewpoint_snippet": str(row.get("Viewpoint", "")).replace("\n", " ").strip(),
                    "bend": str(row.get("BEND", "")).replace("\n", " ").strip(),
                    "bend_reasoning": str(row.get("BEND_Reasoning", "")).replace("\n", " ").strip(),
                }
            )

    result = pd.DataFrame(records)
    if not result.empty:
        result["phase"] = pd.Categorical(result["phase"], categories=PHASE_ORDER, ordered=True)
        result = result.sort_values(["phase", "interactions"], ascending=[True, False]).reset_index(drop=True)
        result["phase"] = result["phase"].astype(str)

    return result


def main(argv: Iterable[str] | None = None) -> None:
    args = parse_args(argv)
    if args.limit <= 0:
        raise SystemExit("--limit must be greater than zero.")

    if not args.input.exists():
        raise SystemExit(f"Input file not found: {args.input}")

    stance_label = normalize_stance_input(args.stance)
    df = prepare_dataframe(args)
    result = export_representatives(df, stance_label=stance_label, limit=args.limit)

    if args.output:
        output_path = args.output
    else:
        sanitized_stance = stance_label.lower().replace(" ", "_")
        output_path = args.input.with_name(
            f"{args.input.stem}_{sanitized_stance}_top{args.limit}_per_phase.csv"
        )
    result.to_csv(output_path, index=False, encoding="utf-8")
    print(f"Wrote {len(result)} rows to {output_path}")


if __name__ == "__main__":
    main()
